重要事项!
www.sovereignEngine.cn
一、PWM 乐鑫ESP-IDF开发技术文档
LED PWM 控制器 - ESP32 - — ESP-IDF 编程指南 latest 文档
脉宽调制(Pulse-Width Modulation,PWM)是利用微处理器的数字输出,来对模拟电路进行控制的一种非常有效的技术,通过对一系列脉冲的宽度进行调制,来等效的获得所需要的波形(含形状和幅值),即通过改变导通时间占总时间的比例,也就是占空比,达到调整电压和频率的目的。
二、PWM是什么?(LEDC,控制电机的是MCPWM)

LED 控制器 (LEDC) 主要用于控制 LED,也可产生 PWM 信号用于其他设备的控制。该控制器有 8 路通道,可以产生独立的波形,驱动 RGB LED 等设备。
LEDC 通道共有两组,分别为 8 路高速通道和 8 路低速通道。高速通道模式在硬件中实现,可以自动且无干扰地改变 PWM 占空比。低速通道模式下,PWM 占空比需要由软件中的驱动器改变。每组通道都可以使用不同的时钟源。
LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实现亮度和颜色渐变。
三、如何配置ESP-IDF PWM?


3.1 LED PWM配置流程(LEDC)
- 速度模式 ledc_mode_t
- ESP32 LEDC 时钟源特性
3.2 备注
首次 LEDC 配置时,建议先配置定时器(调用函数 ledc_timer_config()),再配置通道(调用函数 ledc_channel_config())。这样可以确保 IO 脚上的 PWM 信号自有输出开始其频率就是正确的。确保配置顺序遵循ledc_timer_config() → ledc_channel_config() → (可选)设置占空比/渐变
4.0 三个PWM(LEDC)项目对比
4.1 怎么做简单的PWM亮度渐变(硬件控制)
把时钟源想象成一根水管,水流速度固定:
- 时钟源(f_时钟源):水管的原始水流速度,例如 APB_CLK = 80 MHz,就是每秒 8000 万个脉冲。这是"原材料"。
- 分频系数(LEDC_CLK_DIV):相当于给水管加了一个节流阀,把水流速度降低。比如分频系数为 5000,水流速度就变成 16000Hz。
- 分辨率(2^N):计数器从 0 数到 2^N - 1 才算完成一个 PWM 周期。N 越大,数的数越多,PWM 频率就越低,但占空比的调节精度越高。

- ****
- ****
- ****
假设你想要:1 kHz 的 PWM,50% 占空比,使用 APB_CLK (80 MHz):
- ****
这就是为什么代码里写 对应 50% 占空比的原因。
总结一句话:时钟源是原料,分辨率决定精细度,频率决定快慢,三者由公式绑定,鱼和熊掌不可兼得——频率越高,精度越低。
#include <stdio.h>
#include "freertos/FreeRTOS.h" // FreeRTOS 实时操作系统核心头文件
#include "freertos/task.h" // FreeRTOS 任务管理(xTaskCreate、vTaskDelay 等)
#include "driver/gpio.h" // GPIO 驱动,用于配置和读取按键引脚
#include "driver/ledc.h" // LEDC(LED PWM 控制器)驱动
#include "esp_err.h" // ESP-IDF 错误码定义(ESP_OK 等)
#include "esp_log.h" // 日志打印(ESP_LOGI、ESP_LOGD 等)
// ===== 宏定义:硬件引脚和 LEDC 参数配置 =====
#define BUTTON_GPIO 20 // 按键连接的 GPIO 引脚号
#define LED_GPIO 2 // LED 连接的 GPIO 引脚号
#define LEDC_TIMER LEDC_TIMER_0 // 使用 LEDC 定时器 0
#define LEDC_MODE LEDC_LOW_SPEED_MODE // 使用低速模式(ESP32 支持高速/低速两种模式)
#define LEDC_CHANNEL LEDC_CHANNEL_0 // 使用 LEDC 通道 0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // 占空比分辨率为 13 位,范围 0~8191
#define LEDC_FREQUENCY 4000 // PWM 信号频率为 4000 Hz
static const char *TAG = "PWM_Breath"; // 日志标签,打印日志时会显示此前缀
// ===== LEDC 初始化函数 =====
static void ledc_init(void)
{
// 第一步:配置定时器
// 定时器决定 PWM 信号的频率和占空比分辨率
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE, // 速度模式:低速
.timer_num = LEDC_TIMER, // 定时器编号:0
.duty_resolution = LEDC_DUTY_RES, // 分辨率:13 位(占空比范围 0~8191)
.freq_hz = LEDC_FREQUENCY, // PWM 频率:4000 Hz
.clk_cfg = LEDC_AUTO_CLK, // 自动选择时钟源
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); // 应用定时器配置,出错则终止程序
// 第二步:配置通道
// 通道将定时器产生的 PWM 信号绑定到具体的 GPIO 引脚
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_MODE, // 速度模式:低速
.channel = LEDC_CHANNEL, // 通道编号:0
.timer_sel = LEDC_TIMER, // 绑定到定时器 0
.intr_type = LEDC_INTR_DISABLE, // 不使用渐变完成中断
.gpio_num = LED_GPIO, // 输出到 GPIO2(LED 引脚)
.duty = 0, // 初始占空比为 0(LED 初始状态为熄灭)
.hpoint = 0, // 高电平起始点为 0(不做相位偏移)
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); // 应用通道配置,出错则终止程序
}
// ===== 呼吸灯任务函数 =====
static void breathing_led_task(void *arg)
{
// 安装 LEDC 渐变功能(硬件渐变需要先调用此函数,它会占用 LEDC 模块的中断)
ESP_ERROR_CHECK(ledc_fade_func_install(0));
for (;;) { // 无限循环,持续检测按键状态
if (gpio_get_level(BUTTON_PIN) == 0) { // 检测到低电平(也就是按键被按下)时往下执行
vTaskDelay(pdMS_TO_TICKS(30)); // 按键消抖
if (gpio_get_level(BUTTON_PIN) == 0) { // 确认是按下,非误触
// 按键按下时,GPIO 电平为低(因为配置了上拉电阻,按下接地变为低电平)
ESP_LOGD(TAG, "Button pressed, start breathing");
// 渐亮:阻塞等待完成,从当前占空比渐变到 8191(最亮),耗时 1000ms
ledc_set_fade_time_and_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0,
8191, 1000, LEDC_FADE_WAIT_DONE);
// 渐灭:非阻塞,立即返回,从 8191 渐变到 0(熄灭),耗时 1000ms
ledc_set_fade_time_and_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0,
0, 1000, LEDC_FADE_NO_WAIT);
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // 延时 50ms,避免 CPU 空转
}
}
// ===== 程序入口 =====
void app_main(void)
{
// 配置按键 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BUTTON_GPIO), // 选择 GPIO20
.mode = GPIO_MODE_INPUT, // 设置为输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 使能内部上拉电阻(未按下时为高电平)
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 不使用下拉
.intr_type = GPIO_INTR_DISABLE, // 不使用 GPIO 中断
};
gpio_config(&io_conf); // 应用 GPIO 配置
ledc_init(); // 初始化 LEDC 定时器和通道
ESP_LOGI(TAG, "Program started. Press button on GPIO %d for breathing LED.", BUTTON_GPIO);
// 打印启动日志,提示用户按下按键
// 创建呼吸灯任务
// 参数依次为:任务函数、任务名称、栈大小(字节)、参数、优先级、任务句柄
xTaskCreate(breathing_led_task, "breathing_led", 4096, NULL, 5, NULL);
}4.2 如何实现ESP-IDF框架下PWM结合GPIO上拉输入的短按切换模式(呼吸灯与闪烁)?
INFO
核心特点:
流程:
**button_task:检测下降沿 → led_mode = (led_mode % 2) + 1(在1和2之间切换)
**led_task:
**** mode=1 → 非阻塞渐亮 → 每10ms检查模式 → 非阻塞渐灭 → 循环
**** mode=2 → 亮100ms → 灭100ms → 循环
**** mode=0 → 熄灭,等待50ms
#include <stdio.h>
#include "freertos/FreeRTOS.h" // FreeRTOS 实时操作系统核心头文件
#include "freertos/task.h" // FreeRTOS 任务管理(创建任务、延时等)
#include "driver/gpio.h" // GPIO 驱动(配置引脚、读取电平)
#include "driver/ledc.h" // LEDC 驱动(LED PWM 控制器)
#include "esp_err.h" // ESP-IDF 错误码处理
#include "esp_log.h" // ESP-IDF 日志打印
// ===== 宏定义:硬件引脚和 LEDC 参数 =====
#define BUTTON_GPIO 20 // 按键连接的 GPIO 引脚编号
#define LED_GPIO 2 // LED 连接的 GPIO 引脚编号
#define LEDC_TIMER LEDC_TIMER_0 // 使用 LEDC 定时器 0(共 4 个可选)
#define LEDC_MODE LEDC_LOW_SPEED_MODE // 低速模式(大多数 ESP32 芯片支持)
#define LEDC_CHANNEL LEDC_CHANNEL_0 // 使用 LEDC 通道 0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // PWM 分辨率:13 位(占空比范围 0~8191)
#define LEDC_FREQUENCY 4000 // PWM 频率:4000 Hz
static const char *TAG = "PWM_Breath"; // 日志标签,打印日志时会显示此名称
// 全局模式变量(volatile 防止编译器优化,保证多任务访问时读到最新值)
// 0 = 关闭,1 = 呼吸灯,2 = 闪烁
static volatile int led_mode = 0;
// ===== LEDC 初始化函数 =====
static void ledc_init(void)
{
// 第一步:配置定时器,决定 PWM 的频率和分辨率
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE, // 低速模式
.timer_num = LEDC_TIMER, // 使用定时器 0
.duty_resolution = LEDC_DUTY_RES, // 13 位分辨率
.freq_hz = LEDC_FREQUENCY, // 频率 4000 Hz
.clk_cfg = LEDC_AUTO_CLK, // 自动选择时钟源
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); // 应用配置,出错则打印并停止
// 第二步:配置通道,将 PWM 信号绑定到具体 GPIO 引脚
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_MODE, // 低速模式
.channel = LEDC_CHANNEL, // 通道 0
.timer_sel = LEDC_TIMER, // 绑定定时器 0
.intr_type = LEDC_INTR_DISABLE, // 不使用中断
.gpio_num = LED_GPIO, // 输出到 LED 引脚(GPIO2)
.duty = 0, // 初始占空比为 0(LED 熄灭)
.hpoint = 0, // 高电平起始点为 0(无相位偏移)
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); // 应用通道配置
}
// ===== 按键检测任务 =====
// 功能:检测按键下降沿(按下),短按切换模式(1→2→1 循环)
static void button_task(void *arg)
{
int last_level = 1; // 上一次按键电平(1=未按下,因为启用了上拉电阻)
while (1) {
int level = gpio_get_level(BUTTON_GPIO); // 读取当前按键电平
// 检测下降沿:上次高电平,现在低电平 → 按键刚被按下
if (last_level == 1 && level == 0) {
vTaskDelay(pdMS_TO_TICKS(20)); // 延时 20ms 去抖动(消除机械抖动干扰)
if (gpio_get_level(BUTTON_GPIO) == 0) { // 再次确认确实是按下状态
// 在模式 1 和 2 之间切换:1→2→1→...
led_mode = (led_mode % 2) + 1;
ESP_LOGI(TAG, "Button pressed, mode -> %d", led_mode);
}
}
last_level = level; // 更新上一次电平状态
vTaskDelay(pdMS_TO_TICKS(10)); // 每 10ms 检测一次,释放 CPU 给其他任务
}
}
// ===== LED 控制任务 =====
static void led_task(void *arg)
{
// 安装 LEDC 渐变功能(呼吸灯效果必须先调用此函数)
ESP_ERROR_CHECK(ledc_fade_func_install(0));
int current_mode = 0; // 记录当前正在执行的模式,用于检测模式切换
while (1) {
int mode = led_mode; // 读取全局模式变量
if (mode == 1) {
// ===== 呼吸灯模式 =====
if (current_mode != 1) {
current_mode = 1;
ESP_LOGI(TAG, "Breathing mode"); // 首次进入时打印日志
}
// 渐亮:1000ms 内将占空比渐变到 8191(最亮)
ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL, 8191, 1000);
ledc_fade_start(LEDC_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT); // 非阻塞启动渐变
// 等待 1000ms,每 10ms 检查一次模式是否被切换
for (int i = 0; i < 100; i++) {
vTaskDelay(pdMS_TO_TICKS(10));
if (led_mode != 1) goto mode_changed; // 模式变了,跳转到清理逻辑
}
// 渐灭:1000ms 内将占空比渐变到 0(最暗)
ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL, 0, 1000);
ledc_fade_start(LEDC_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT);
for (int i = 0; i < 100; i++) {
vTaskDelay(pdMS_TO_TICKS(10));
if (led_mode != 1) goto mode_changed;
}
} else if (mode == 2) {
// ===== 闪烁模式 =====
if (current_mode != 2) {
current_mode = 2;
ESP_LOGI(TAG, "Blink mode");
}
// 点亮 LED:占空比设为 8191(最大亮度),调用 update 立即生效
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 8191);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL); // 注意:set_duty 后必须调用 update_duty 才能生效
vTaskDelay(pdMS_TO_TICKS(100)); // 亮 100ms
if (led_mode != 2) goto mode_changed; // 检查模式是否切换
// 熄灭 LED:占空比设为 0
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
vTaskDelay(pdMS_TO_TICKS(100)); // 灭 100ms
} else {
// ===== 关闭模式(mode == 0)=====
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0); // 占空比设为 0
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL); // 立即更新
vTaskDelay(pdMS_TO_TICKS(50)); // 等待 50ms 再检查
}
continue; // 继续下一次循环
mode_changed:
// 模式切换时的清理:先关闭 LED,再重置当前模式标记
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
current_mode = 0; // 重置,下次进入新模式时会重新打印日志
}
}
// ===== 程序入口函数 =====
void app_main(void)
{
// 配置按键 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BUTTON_GPIO), // 选择 GPIO20 作为按键引脚
.mode = GPIO_MODE_INPUT, // 设置为输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 启用内部上拉(未按下时为高电平)
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 不启用下拉
.intr_type = GPIO_INTR_DISABLE, // 不使用中断(用轮询方式检测按键)
};
gpio_config(&io_conf); // 应用 GPIO 配置
ledc_init(); // 初始化 LEDC(PWM 控制器)
// 打印启动日志
ESP_LOGI(TAG, "Program started. Press button on GPIO %d to switch mode.", BUTTON_GPIO);
// 创建按键检测任务(优先级 6,高于 led_task,确保按键响应及时)
xTaskCreate(button_task, "button_task", 2048, NULL, 6, NULL);
// 创建 LED 控制任务(优先级 5,栈大小 4096 字节,因呼吸灯逻辑较复杂)
xTaskCreate(led_task, "led_task", 4096, NULL, 5, NULL);
// app_main 函数返回后,主任务会被自动删除
// 但 button_task 和 led_task 会继续在后台运行
}4.3 如何实现ESP-IDF框架下PWM结合GPIO上拉输入的长短按模式(状态控制开关、呼吸与闪烁)?
INFO
核心特点:
流程:
button_task:
** 检测下降沿 → 记录 press_start 时刻**
** 检测上升沿 → 计算 held_time**
** held_time >= 1000ms → led_mode = 0(关闭)**
** held_time < 1000ms → led_mode 在1和2之间切换**
led_task:与代码二完全相同
#include <stdio.h>
#include "freertos/FreeRTOS.h" // FreeRTOS 实时操作系统核心头文件
#include "freertos/task.h" // FreeRTOS 任务管理(创建任务、延时等)
#include "driver/gpio.h" // GPIO 驱动(配置引脚输入输出、读取电平)
#include "driver/ledc.h" // LEDC 驱动(LED PWM 控制器,用于生成 PWM 信号)
#include "esp_err.h" // ESP-IDF 错误码处理
#include "esp_log.h" // ESP-IDF 日志打印
// ===== 宏定义:硬件引脚和 LEDC 参数 =====
#define BUTTON_GPIO 20 // 按键连接的 GPIO 引脚编号
#define LED_GPIO 2 // LED 连接的 GPIO 引脚编号
#define LEDC_TIMER LEDC_TIMER_0 // 使用 LEDC 定时器 0
#define LEDC_MODE LEDC_LOW_SPEED_MODE // 使用低速模式(大多数 ESP32 芯片支持)
#define LEDC_CHANNEL LEDC_CHANNEL_0 // 使用 LEDC 通道 0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // PWM 分辨率:13 位(占空比范围 0~8191)
#define LEDC_FREQUENCY 4000 // PWM 频率:4000 Hz
static const char *TAG = "PWM_Breath"; // 日志标签,打印日志时会显示此名称
// 全局变量:当前 LED 模式(volatile 保证多任务访问时不被编译器优化掉)
// 0 = 关闭,1 = 呼吸灯,2 = 闪烁
static volatile int led_mode = 0;
// ===== LEDC 初始化函数 =====
static void ledc_init(void)
{
// 配置 LEDC 定时器:决定 PWM 的频率和分辨率
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE, // 低速模式
.timer_num = LEDC_TIMER, // 使用定时器 0
.duty_resolution = LEDC_DUTY_RES, // 13 位分辨率
.freq_hz = LEDC_FREQUENCY, // 频率 4000 Hz
.clk_cfg = LEDC_AUTO_CLK, // 自动选择时钟源
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); // 应用定时器配置,出错则打印并停止
// 配置 LEDC 通道:将 PWM 信号绑定到具体的 GPIO 引脚
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_MODE, // 低速模式
.channel = LEDC_CHANNEL, // 通道 0
.timer_sel = LEDC_TIMER, // 绑定定时器 0
.intr_type = LEDC_INTR_DISABLE, // 不使用中断
.gpio_num = LED_GPIO, // 输出到 LED 引脚(GPIO2)
.duty = 0, // 初始占空比为 0(LED 熄灭)
.hpoint = 0, // 高电平起始点为 0(无相位偏移)
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); // 应用通道配置
}
// ===== 按键检测任务 =====
// 功能:短按切换模式(1→2→1 循环),长按超过 1 秒松开后关闭 LED
static void button_task(void *arg)
{
int last_level = 1; // 上一次按键电平(1=未按下,因为启用了上拉电阻)
TickType_t press_start = 0; // 记录按键按下的时刻(FreeRTOS 系统 tick)
while (1) {
int level = gpio_get_level(BUTTON_GPIO); // 读取当前按键电平
// 检测下降沿:上次是高电平,现在是低电平 → 按键刚被按下
if (last_level == 1 && level == 0) {
vTaskDelay(pdMS_TO_TICKS(20)); // 延时 20ms 去抖动(消除机械抖动干扰)
if (gpio_get_level(BUTTON_GPIO) == 0) { // 再次确认确实是按下状态
press_start = xTaskGetTickCount(); // 记录按下时刻(单位:tick)
ESP_LOGD(TAG, "Button pressed"); // 调试日志
}
}
// 检测上升沿:上次是低电平,现在是高电平 → 按键刚被松开
if (last_level == 0 && level == 1) {
vTaskDelay(pdMS_TO_TICKS(20)); // 延时 20ms 去抖动
if (gpio_get_level(BUTTON_GPIO) == 1) { // 再次确认确实是松开状态
// 计算按键持续时间(tick 差值 × 每 tick 毫秒数)
TickType_t held_time = (xTaskGetTickCount() - press_start) * portTICK_PERIOD_MS;
if (held_time >= 1000) {
// 长按(超过 1 秒):关闭 LED
led_mode = 0;
ESP_LOGI(TAG, "Long press released (>1s), LED off");
} else {
// 短按:在模式 1 和 2 之间切换(1→2→1→...)
led_mode = (led_mode % 2) + 1;
ESP_LOGI(TAG, "Short press, mode -> %d", led_mode);
}
}
}
last_level = level; // 更新上一次电平状态
vTaskDelay(pdMS_TO_TICKS(10)); // 每 10ms 检测一次按键,释放 CPU 给其他任务
}
}
// ===== LED 控制任务 =====
static void led_task(void *arg)
{
ESP_ERROR_CHECK(ledc_fade_func_install(0)); // 安装 LEDC 渐变功能(用于呼吸灯效果)
int current_mode = 0; // 记录当前正在执行的模式,用于检测模式切换
while (1) {
int mode = led_mode; // 读取全局模式变量
if (mode == 1) {
// ===== 呼吸灯模式 =====
if (current_mode != 1) {
current_mode = 1;
ESP_LOGI(TAG, "Breathing mode"); // 首次进入时打印日志
}
// 渐亮:在 1000ms 内将占空比从当前值渐变到 8191(最亮)
ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL, 8191, 1000);
ledc_fade_start(LEDC_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT); // 非阻塞启动渐变
// 等待 1000ms,每 10ms 检查一次模式是否被切换
for (int i = 0; i < 100; i++) {
vTaskDelay(pdMS_TO_TICKS(10));
if (led_mode != 1) goto mode_changed; // 模式变了,跳转到清理逻辑
}
// 渐灭:在 1000ms 内将占空比从当前值渐变到 0(最暗)
ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL, 0, 1000);
ledc_fade_start(LEDC_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT);
for (int i = 0; i < 100; i++) {
vTaskDelay(pdMS_TO_TICKS(10));
if (led_mode != 1) goto mode_changed;
}
} else if (mode == 2) {
// ===== 闪烁模式 =====
if (current_mode != 2) {
current_mode = 2;
ESP_LOGI(TAG, "Blink mode");
}
// 点亮 LED:设置占空比为 8191(最大亮度)并立即更新
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 8191);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
vTaskDelay(pdMS_TO_TICKS(100)); // 亮 100ms
if (led_mode != 2) goto mode_changed; // 检查模式是否切换
// 熄灭 LED:设置占空比为 0
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
vTaskDelay(pdMS_TO_TICKS(100)); // 灭 100ms
} else {
// ===== 关闭模式(mode == 0)=====
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0); // 占空比设为 0
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL); // 立即更新
vTaskDelay(pdMS_TO_TICKS(50)); // 等待 50ms 再检查
}
continue; // 继续下一次循环
mode_changed:
// 模式切换时的清理:先关闭 LED,再重置当前模式标记
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
current_mode = 0; // 重置,下次进入
}
}
// ===== 程序入口函数 =====
void app_main(void)
{
// 配置按键 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BUTTON_GPIO), // 选择 GPIO20 作为按键引脚
.mode = GPIO_MODE_INPUT, // 设置为输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 启用内部上拉电阻(未按下时为高电平)
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 不启用下拉
.intr_type = GPIO_INTR_DISABLE, // 不使用中断(用轮询方式检测按键)
};
gpio_config(&io_conf); // 应用 GPIO 配置
ledc_init(); // 初始化 LEDC(PWM 控制器)
ESP_LOGI(TAG, "Program started. Press button on GPIO %d to switch mode.", BUTTON_GPIO);
// 打印启动日志,提示用户按下按键切换模式
// 创建按键检测任务
// 参数依次:任务函数、任务名称、栈大小(字节)、参数、优先级、任务句柄
xTaskCreate(button_task, "button_task", 2048, NULL, 6, NULL);
// 优先级 6,高于 led_task,确保按键响应及时
// 创建 LED 控制任务
xTaskCreate(led_task, "led_task", 4096, NULL, 5, NULL);
// 优先级 5,栈稍大(4096字节),因为呼吸灯逻辑更复杂
}